package notesDetection.Utilities;

public class GeneralNoteDetection {

	
	private double _lowestAFreq;
	
	/**
	 * @param minimum
	 * @param maximum
	 * @return Random integer x where minimum <= x <= maximum.
	 * The distribution is ALMOST uniform (close enough) 
	 */
	public static int GetRandomInt(int minimum, int maximum)
	{	
		return minimum + (int)Math.round((maximum - minimum) * Math.random());
	}
	
	/**
	 * A (the note "LA") is usually 440 Hz (or 440 multiplied/divided by a power of 2),
	 * but may be different...
	 * 
	 * @param frequencyHzOfA
	 */
	public GeneralNoteDetection(double frequencyHzOfA)
	{
		//	frequency of A will be reduced (lowered down by OCTAVES)
		//	to the nearest A pitch <= threshold below:
		double NOTES_DETECTION_THRESHOLD = 55.0;
		
		if(frequencyHzOfA <= 0.0)
		{
			_lowestAFreq = Double.NaN;
			return;
		}
		if(frequencyHzOfA < NOTES_DETECTION_THRESHOLD)
		{
			//	Frequency selected is a bad configuration, but...
			while(frequencyHzOfA < NOTES_DETECTION_THRESHOLD / 2)
			{
				frequencyHzOfA *= 2;
			}
			//	This could also have been done more efficiently
			
		}
		//	We can do it quicker this way (no loop):
		//	frequencyOfA /= Math.pow(2, Math.Ceiling(Log2(frequencyHzOfA / NOTES_DETECTION_THRESHOLD)));
		//	BUT: The following form is more readable:
		while(frequencyHzOfA > NOTES_DETECTION_THRESHOLD)
		{
			frequencyHzOfA /= 2;
		}
		
		_lowestAFreq = frequencyHzOfA;
	}
	
	/**
	 *	Default CTOR uses A = 55 Hz (same tuning as A = 440 Hz)
	 */
	public GeneralNoteDetection()
	{
		_lowestAFreq = 55.0;
	}
	
	public Note GetFrequencyAsNote(double frequncy)
	{
		if(frequncy < _lowestAFreq)
		{
			return Note.InvalidNote;
		}
		
		//	Get the note normalized to a scale of 1.0-2.0
		double normalizedFrequency = frequncy / (Math.pow(2, Math.floor(Math.log(frequncy / _lowestAFreq)/Math.log(2))) * _lowestAFreq);
		//	Measure how many semi-tones the value is from base (note "A") and round to nearest
		int shiftFromA = (int)Math.round(12 * Math.log(normalizedFrequency) / Math.log(2));
		
		switch(shiftFromA)
		{
		case 0:
		case 12:
			return Note.A;
		case 1:
			return Note.Bb;
		case 2:
			return Note.B;
		case 3:
			return Note.C;
		case 4:
			return Note.Db;
		case 5:
			return Note.D;
		case 6:
			return Note.Eb;
		case 7:
			return Note.E;
		case 8:
			return Note.F;
		case 9:
			return Note.Gb;
		case 10:
			return Note.G;
		case 11:
			return Note.Ab;
		default:
			return Note.InvalidNote;
		}
	}
	
	public enum Note
	{
		A,
		Bb,
		B,
		C,
		Db,
		D,
		Eb,
		E,
		F,
		Gb,
		G,
		Ab,
		QuietSegment,
		InvalidNote
	}
}
